#ifndef GAME_ANIMATION_GEOMETRY_GEOMETRY_H
#define GAME_ANIMATION_GEOMETRY_GEOMETRY_H
//
// Game Animation.
// Copyright (C) 2007 Department of Computer Science, University of Copenhagen
//
#include <animation/configuration.h>

#include <OpenTissue/utility/GL/gl_util.h>

#include <vector>
#include <cassert>

namespace animation
{

  /**
   * Geometry Class.
   * This class holds the data if a face-index array (triangle mesh) and provides
   * various openGL methods for efficiently rendering of the mesh data.
   *
   */
  template<typename types>
  class Geometry
  {
  public:

    typedef typename types::geometry_type     geometry_type;
    typedef typename types::math_types     math_types;

    typedef typename types::vector2_type      vector2_type;
    typedef typename types::vector3_type      vector3_type;
    typedef typename types::vector4_type      vector4_type;
    
    typedef typename types::vertex_type       vertex_type;
    typedef typename types::real_type         real_type;

    typedef std::vector<vertex_type>             vertex_container;
    typedef typename vertex_container::iterator  vertex_iterator;
    typedef std::vector<unsigned int>            index_container;

  public:

    /**
     * The Coord Iterator.
     * This iterator makes it possibel to iterate over the vertices
     * and acessing them as though they were a simple vector3_type
     * holding just the coordinate of a vertex.
     *
     * This makes it quite easy to for instance fit a tight AABB
     * enclosing all the geometry.
     */
    class coord_iterator
    {
    protected:

      unsigned int    m_idx;
      geometry_type * m_owner;

    public:

      coord_iterator()
        : m_idx(0)
        , m_owner(0)
      {}

      coord_iterator(geometry_type const * owner,unsigned int idx)
        : m_idx(idx)
        , m_owner(const_cast<geometry_type*>(owner))
      {}

      coord_iterator(coord_iterator const & c)
        : m_idx(c.m_idx)
        , m_owner(c.m_owner)
      {}

      coord_iterator &operator=(coord_iterator const & c) 
      {  
        m_idx = c.m_idx; 
        m_owner = c.m_owner; 
      };

    public:

      bool operator==(coord_iterator const& other) const { return (m_owner==other.m_owner && m_idx == other.m_idx); }
      bool operator!=(coord_iterator const& other) const { return !( *this == other ); }

      coord_iterator & operator++()
      {
        ++m_idx;
        return *this;
      }

      coord_iterator & operator--()
      {
        --m_idx;
        return *this;
      }

      vector3_type const & operator*() const { return m_owner->m_vertices[m_idx].m_coord; }
      vector3_type const * operator->() const { return &(m_owner->m_vertices[m_idx].m_coord); }

    };

    coord_iterator coord_begin()const { return coord_iterator(this,0); }
    coord_iterator coord_end()const { return coord_iterator(this,m_vertices.size()); }

  protected:

    unsigned int      m_index_buffer_ID;     ///< Vertex Object Specific.
    unsigned int      m_index_count;
    unsigned int      m_geometry_buffer_ID;  ///< Vertex Object Specific.
    unsigned int      m_vertex_count;
    unsigned int      m_quads;

  public:
    vertex_container  m_vertices;
    index_container   m_indices;

    vertex_type & get_vertex(unsigned int const & idx) { return m_vertices[idx]; };
    vertex_type const & get_vertex(unsigned int const & idx) const { return m_vertices[idx]; };

    unsigned int & get_index(unsigned int const & idx) { return m_indices[idx]; };
    unsigned int const & get_index(unsigned int const & idx) const { return m_indices[idx]; };

    unsigned int vertices() const { return m_vertices.size(); }
    unsigned int indices() const { return m_indices.size(); }

  public:

    Geometry()
      :  m_index_count(0)
      , m_geometry_buffer_ID(0)
      , m_vertex_count(0)
      , m_vertices(0)
      , m_indices(0)
    { }

    /**
    * Special Constructor.
    *
    * @param vertices   The number of vertices in the geometry.
    * @param indices    The number of indices in the geometry.
    */
    Geometry(unsigned int const & vertices,unsigned int const & indices)
      : m_index_count(indices)
      , m_geometry_buffer_ID(0)
      , m_vertex_count(vertices)
      , m_index_buffer_ID(0)
      , m_vertices(m_vertex_count)
      , m_indices(m_index_count)
    { }
	void merge(Geometry & geometry,float x,float y,float z){ 
		
		if(m_vertices.max_size()-m_vertex_count<geometry.vertices()){
			std::cout<<"Out of space:cap "<<m_vertices.max_size()<<"cur "<<m_vertex_count<<"new "<<geometry.vertices()<<std::endl;
		}m_vertex_count+=geometry.vertices();
		for(vertex_iterator i=geometry.m_vertices.begin();i!=geometry.m_vertices.end();++i){
			vertex_type c;
			
			c.m_normal=i->m_normal;

			c.m_coord(0)=i->m_coord(0)+x;
			c.m_coord(1)=i->m_coord(1)+y;
			c.m_coord(2)=i->m_coord(2)+z;
			c.m_tex0[0]=i->m_tex0[0];
			c.m_tex0[1]=i->m_tex0[1];

			c.m_tex1[0]=i->m_tex1[0];
			c.m_tex1[1]=i->m_tex1[1];

			c.m_tex2[0]=i->m_tex2[0];
			c.m_tex2[1]=i->m_tex2[1];
			c.m_tex2[2]=i->m_tex2[2];
			c.m_tex2[3]=i->m_tex2[3];

			c.m_color[0]=i->m_color[0];
			c.m_color[1]=i->m_color[1];
			c.m_color[2]=i->m_color[2];
			c.m_color[3]=i->m_color[3];
			m_vertices.push_back(c);
		}
		//m_vertices.insert(m_vertices.end(),geometry.m_vertices.begin(),geometry.m_vertices.end());

		for(index_container::iterator i=geometry.m_indices.begin();i!=geometry.m_indices.end();++i){
			m_indices.push_back((*i)+m_index_count);
		}
		m_index_count+=geometry.indices();	
	}

    ~Geometry() { cleanup_vertex_object(); }

  protected:

    void setup_vertex_object()
    {
      if(m_geometry_buffer_ID)
        return;
      glGenBuffersARB(1, &m_index_buffer_ID);
      glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, m_index_buffer_ID);
      glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, m_index_count * sizeof(unsigned int), &m_indices[0], GL_STATIC_DRAW_ARB);
      OpenTissue::gl::gl_check_errors();
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
      glGenBuffersARB(1, &m_geometry_buffer_ID);    
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_geometry_buffer_ID);
      glBufferDataARB(GL_ARRAY_BUFFER_ARB, m_vertex_count * sizeof(vertex_type), NULL, GL_STATIC_DRAW_ARB);
      OpenTissue::gl::gl_check_errors();
      vertex_type * vertices = (vertex_type *)glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);
      std::copy(m_vertices.begin(),m_vertices.end(),vertices);
      glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
    };

  public:

    void cleanup_vertex_object()
    {
      if(m_geometry_buffer_ID)
        glDeleteBuffersARB(1, &m_geometry_buffer_ID);
      m_geometry_buffer_ID = 0;
      if(m_index_buffer_ID)
        glDeleteBuffersARB(1, &m_index_buffer_ID);
      m_index_buffer_ID = 0;
    }

    void vertex_object_render()
    {
      if(!m_vertex_count || !m_index_count)
        return;

      setup_vertex_object();

      if(!m_geometry_buffer_ID)
        return;

      if(m_geometry_buffer_ID)
      {
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_geometry_buffer_ID); 

        static int offset_coord     = 0;
        static int offset_normal    = sizeof(vector3_type) + offset_coord;
        static int offset_color     = sizeof(vector3_type) + offset_normal;
        static int offset_tex0      = sizeof(vector4_type) + offset_color;
        static int offset_tex1      = sizeof(vector2_type) + offset_tex0;
        static int offset_tex2      = sizeof(vector2_type) + offset_tex1;

        GLsizei stride         = sizeof(vertex_type);
#define OFFSET(x) ((char *)NULL+(x))
        glClientActiveTexture(GL_TEXTURE0);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer( 2, GL_FLOAT, stride, OFFSET(offset_tex0)   );

        glClientActiveTexture(GL_TEXTURE1);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer( 2, GL_FLOAT, stride, OFFSET(offset_tex1)   );

        glClientActiveTexture(GL_TEXTURE2);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer( 4, GL_FLOAT, stride, OFFSET(offset_tex2)   );

        glEnableClientState(GL_NORMAL_ARRAY);
        glNormalPointer  (    GL_FLOAT, stride, OFFSET(offset_normal) );

        glEnableClientState(GL_COLOR_ARRAY);
        glColorPointer   ( 4, GL_FLOAT, stride, OFFSET(offset_color)  );

        glEnableClientState(GL_VERTEX_ARRAY);
        glVertexPointer  ( 3, GL_FLOAT, stride, OFFSET(offset_coord)  );
#undef OFFSET
      }
      if(m_index_buffer_ID)
      {
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, m_index_buffer_ID);
        glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, 0);
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
      }
      else
      {
        glDrawRangeElements(GL_TRIANGLES, 0, m_index_count, m_index_count, GL_UNSIGNED_INT, &m_indices[0]);
      }

      if(m_geometry_buffer_ID)
      {
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); 

        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);
        glClientActiveTexture(GL_TEXTURE2);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glClientActiveTexture(GL_TEXTURE1);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glClientActiveTexture(GL_TEXTURE0);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
      }
    }

    void vertex_array_render()
    {
      GLvoid * ptr_coord     = &(m_vertices[0]);
      GLvoid * ptr_normal    = &(m_vertices[0].m_normal);
      GLvoid * ptr_color     = &(m_vertices[0].m_color);
      GLvoid * ptr_tex0      = &(m_vertices[0].m_tex0);
      GLvoid * ptr_tex1      = &(m_vertices[0].m_tex1);
      GLvoid * ptr_tex2      = &(m_vertices[0].m_tex2);
      GLsizei stride         = sizeof(vertex_type);

      glClientActiveTexture(GL_TEXTURE0);
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
      glTexCoordPointer( 2, GL_FLOAT, stride, ptr_tex0   );

      glClientActiveTexture(GL_TEXTURE1);
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
      glTexCoordPointer( 2, GL_FLOAT, stride, ptr_tex1   );

      glClientActiveTexture(GL_TEXTURE2);
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
      glTexCoordPointer( 4, GL_FLOAT, stride, ptr_tex2   );

      glEnableClientState(GL_NORMAL_ARRAY);
      glNormalPointer  (    GL_FLOAT, stride, ptr_normal );

      glEnableClientState(GL_COLOR_ARRAY);
      glColorPointer   ( 4, GL_FLOAT, stride, ptr_color  );

      glEnableClientState(GL_VERTEX_ARRAY);
      glVertexPointer  ( 3, GL_FLOAT, stride, ptr_coord  );

      glDrawElements(GL_TRIANGLES, m_indices.size(), GL_UNSIGNED_INT, &m_indices[0]);

      glDisableClientState(GL_NORMAL_ARRAY);
      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_COLOR_ARRAY);
      glClientActiveTexture(GL_TEXTURE2);
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
      glClientActiveTexture(GL_TEXTURE1);
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
      glClientActiveTexture(GL_TEXTURE0);
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    };

    void brute_opengl_render(unsigned int mode = GL_TRIANGLES)
    {
      for(unsigned int j=0;j<m_index_count;j=j+3)
      {
        glBegin(mode);
        for(unsigned int c=0;c<3;++c)
        {
          vertex_type & g = m_vertices[m_indices[j+c]];
          glMultiTexCoord2fv(GL_TEXTURE0,g.m_tex0);
          glMultiTexCoord2fv(GL_TEXTURE1,g.m_tex1);
          glMultiTexCoord4fv(GL_TEXTURE2,g.m_tex2);
          glColor4fv(g.m_color);
          glNormal3f(g.m_normal[0],g.m_normal[1],g.m_normal[2]);
          glVertex3f(g.m_coord[0],g.m_coord[1],g.m_coord[2]);
        }
        glEnd();
      }
    }
  };

} // namespace animation

// GAME_ANIMATION_GEOMETRY_GEOMETRY_H
#endif
